import React, { useState, useRef, useEffect } from 'react';
// 定义组件Props
interface LazyloadProps {
  children: React.ReactNode;
  loading: React.ReactNode;
  scrollContainer?: HTMLElement;
  offset?: number;
  resize?: boolean;
  debounce?: number;
  throttle?: number;
}
// 需要监听的事件
const DEFAULT_EVENTS = [
  'scroll',
  'wheel',
  'mousewheel',
  'animationend',
  'transitionend',
  'touchmove',
];

const LazyLoad: React.FC<LazyloadProps> = (props) => {
  // 设置Props默认值
  const {
    children,
    loading,
    scrollContainer = document.body,
    offset = 0,
    debounce = 0,
    throttle = 0,
    resize = false
  } = props;
  // 是否可见
  const [isVisible, setVisible] = useState(false);
  // 包裹容器
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    // 检查函数
    let checkVisible = () => {
      // 如果容器不存在则不计算
      if (!containerRef.current) return;
      // 获取当前组件位置
      const { top, left, width, height } = containerRef.current?.getBoundingClientRect();
      // 获取滚动容器位置
      const {
        top: parentTop,
        left: parentLeft,
        width: parentWidth,
        height: parentHeight
      } = scrollContainer.getBoundingClientRect();
      // 计算屏幕高度和宽度
      const windowInnerHeight = window.innerHeight || document.documentElement.clientHeight;
      const windowInnerWidth = window.innerWidth || document.documentElement.clientWidth;
      // 计算滚动容器和屏幕可视区的相交区域
      const intersectionTop = Math.min(parentTop, 0);
      const intersectionLeft = Math.min(parentLeft, 0);

      const intersectionHeight = Math.min(windowInnerHeight, parentTop + parentHeight) - intersectionTop;
      const intersectionWidth = Math.min(windowInnerWidth, parentLeft + parentWidth) - intersectionLeft;
      // 计算组件距离可视区的高度
      const offsetTop = top - intersectionTop;
      const offsetLeft = left - intersectionLeft;
      // 偏移量计算[top, left]
      const offsets = Array.isArray(offset) ? offset : [offset, offset];
      // 通过上述距离判断组件是否在可见区域
      const isVisible = offsetTop - offsets[0] <= intersectionHeight &&
          offsetTop + height + offsets[0] >= 0 &&
          offsetLeft - offsets[1] <= intersectionWidth &&
          offsetLeft + width + offsets[1] >= 0;

      setVisible(isVisible);
      // 如果可见取消当前组件的所有的监听
      if (isVisible) {
        DEFAULT_EVENTS.forEach((event) => {
          scrollContainer?.removeEventListener(event, checkVisible);
        });
        window.removeEventListener('resize', checkVisible);
      }
    }
    // 使用防抖节流增强checkVisbile函数，这里优先防抖，其次节流
    if (debounce) {
      // 防抖函数
      checkVisible = debounceFunc(checkVisible, debounce);
    } else if (throttle) {
      // 节流函数
      checkVisible = throttleFunc(checkVisible, throttle);
    }
    // 批量监听事件
    DEFAULT_EVENTS.forEach((event) => {
      scrollContainer?.addEventListener(event, checkVisible);
    });

    if (resize) {
      window.addEventListener('resize', checkVisible);
    }
    // 自动执行一次
    checkVisible();

    return () => {
      DEFAULT_EVENTS.forEach((event) => {
        scrollContainer?.removeEventListener(event, checkVisible);
      })
      window.removeEventListener('resize', checkVisible);
    }
  }, [scrollContainer, containerRef.current])

  return (
      <div ref={containerRef}>
        { isVisible ? children : loading }
      </div>
  )
}


export default LazyLoad;

function debounceFunc(fn: any, wait: number) {
  let timer:any = null;
  return function () {
    let args = arguments;

    timer && clearTimeout(timer);

    timer = setTimeout(() => {
      //@ts-ignore
      fn.apply(this, args)
    }, wait);
  }
}


function throttleFunc(fn: any, wait: number) {
  let time = 0, timer:any = null;
  return function () {
    let now = Date.now();
    let args = arguments;
    if (now - time > wait) {
      //@ts-ignore
      fn.apply(this, args);
      time = now;
    } else {
      timer && clearTimeout(timer);
      timer = setTimeout(() => {
        //@ts-ignore
        fn.apply(this, args);
        time = now;
      }, wait);
    }
  }
}
